#pragma once

#include <iostream>
#include <pthread.h>
#include <queue>
#include <mutex>
#include <unistd.h>
using namespace std;

const int gDefaultCap = 5;
// #define INI_MTX(mtx) pthread_mutex_init(&mtx, nullptr)

template <class T>
class BlockQueue
{
private:
    // 真就是表示队列为空
    bool isQueueEmpty()
    {
        return bq_.size() == 0;
    }
    bool isQueueFull()
    {
        return bq_.size() == capacity_; // 真就是表示队列满了
    }

public:
    BlockQueue(int capacity = gDefaultCap)
        : capacity_(capacity)
    {
        // INI_MTX(mtx_);
        pthread_mutex_init(&mtx_, nullptr);
        pthread_cond_init(&Empty_, nullptr);
        pthread_cond_init(&Full_, nullptr);
    }
    // 生产者调用push接口，往里面添加数据
    void push(const T &in)
    {
        // 条件变量中的线程等待函数用在上锁和解锁之间，检查资源是否就绪也是要访问临界资源，所以检查资源这个行为也是在加锁和解锁之间
        pthread_mutex_lock(&mtx_);
        // 1.先检查当前的临界资源是否满足访问条件
        while (isQueueFull()) // 满了生产者就等待
            pthread_cond_wait(&Full_, &mtx_);//唤醒的时候会自动获取锁
        // 2.满足访问条件才能访问临界资源
        bq_.push(in);
        // 当生产者生产的容量超过一半的时候才通知消费者消费
        if (bq_.size() >= capacity_ / 2)
            pthread_cond_signal(&Empty_); // 生产者生产完半容量数据，通知消费者可以拿数据了
        pthread_mutex_unlock(&mtx_);
    }
    // 消费者调用pop接口，往里面拿出数据
    void pop(T *out)
    {
        pthread_mutex_lock(&mtx_);
        while (isQueueEmpty())
            pthread_cond_wait(&Empty_, &mtx_);
        *out = bq_.front();
        bq_.pop();
        pthread_mutex_unlock(&mtx_);
        pthread_cond_signal(&Full_); // 消费者拿到一个数据后，通知生产者可以生产了
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&mtx_);
        pthread_cond_destroy(&Empty_);
        pthread_cond_destroy(&Full_);
    }

private:
    queue<T> bq_;         // 阻塞队列
    int capacity_;        // 容量上限
    pthread_mutex_t mtx_; // 通过互斥锁保证线程安全
    // 用它表示阻塞队列是否为空,空就通知生产者消费
    // 避免频繁申请资源，快满了（超过2/3还是满了由自己定制策略）就告诉消费者来消费，没满就告诉生产者生产
    pthread_cond_t Empty_; // 消费者关心是否为空
    pthread_cond_t Full_;  // 生产者关心是否满
};
